/***********************************************************

win32ras.cpp -- module for interface into RAS

Note that this source file contains embedded documentation.
This documentation consists of marked up text inside the
C comments, and is prefixed with an '@' symbol.  The source
files are processed by a tool called "autoduck" which
generates Windows .hlp files.
@doc

******************************************************************/

#ifndef WINVER
#define WINVER 0x500
#endif

#include "pywintypes.h"
#include "ras.h"
#include "raserror.h"

static PyObject *module_error;
static PyObject *obHandleMap = NULL;

/* error helper */

void SetError(char *msg, char *fnName = NULL, DWORD code = 0)
{
	PyObject *v = Py_BuildValue("(izs)", 0, fnName, msg);
	if (v != NULL) {
		PyErr_SetObject(module_error, v);
		Py_DECREF(v);
	}
}
PyObject *ReturnError(char *msg, char *fnName = NULL, DWORD code = 0)
{
	SetError(msg, fnName, code);
	return NULL;
}
extern "C" __declspec(dllexport)
PyObject *ReturnRasError(char *fnName, long err = 0)
{
	const int bufSize = 512;
	TCHAR buf[bufSize];
	DWORD errorCode = err == 0 ? GetLastError() : err;
	BOOL bHaveMessage = FALSE;
	if (errorCode) {
		bHaveMessage = RasGetErrorString(errorCode, buf, bufSize)==0;
		if (!bHaveMessage) {
			bHaveMessage = (0 != FormatMessage( FORMAT_MESSAGE_FROM_SYSTEM,
				NULL, errorCode, 0, buf, bufSize, NULL));
		}
	}
	if (!bHaveMessage)
		_tcscpy(buf, _T("No error message is available"));
	/* strip trailing cr/lf */
	size_t end = _tcslen(buf)-1;
	if (end>1 && (buf[end-1]==_T('\n') || buf[end-1]==_T('\r')))
		buf[end-1] = 0;
	else
		if (end>0 && (buf[end]==_T('\n') || buf[end]==_T('\r')))
			buf[end]=0;
	PyObject *v = Py_BuildValue("(iNN)", errorCode,
		PyWinCoreString_FromString(fnName),
		PyWinObject_FromTCHAR(buf));
	if (v != NULL) {
		PyErr_SetObject(module_error, v);
		Py_DECREF(v);
	}
	return NULL;
}

#if (WINVER >= 0x500)

// Helpers external so I can avoid LoadLibraries of
// the win2k only functions.
typedef BOOL (*PFNPyWinObject_AsRASEAPUSERIDENTITY)(PyObject *ob, RASEAPUSERIDENTITY **pp);

BOOL myPyWinObject_AsRASEAPUSERIDENTITY( PyObject *ob, RASEAPUSERIDENTITY **pp)
{
	static PFNPyWinObject_AsRASEAPUSERIDENTITY pfnPyWinObject_AsRASEAPUSERIDENTITY = NULL;
	if (pfnPyWinObject_AsRASEAPUSERIDENTITY == NULL) {
#ifdef _DEBUG
		HMODULE hmod = GetModuleHandle(_T("win2kras_d.pyd"));
#else
		HMODULE hmod = GetModuleHandle(_T("win2kras.pyd"));
#endif
		if (hmod==NULL) {
			// _should_ have been imported to get the RASEAPUSERIDENTITY in the first place!
			PyErr_SetString(PyExc_RuntimeError, "Can not locate the 'win2kras' module - has it been imported?");
			return FALSE;
		}
		pfnPyWinObject_AsRASEAPUSERIDENTITY = 
			(PFNPyWinObject_AsRASEAPUSERIDENTITY)GetProcAddress(hmod, "PyWinObject_AsRASEAPUSERIDENTITY");
	}
	if (pfnPyWinObject_AsRASEAPUSERIDENTITY==NULL) {
		PyErr_SetString(PyExc_RuntimeError, "Can not locate the 'PyWinObject_AsRASEAPUSERIDENTITY' function in the 'win2kras' module");
		return FALSE;
	}
	return (*pfnPyWinObject_AsRASEAPUSERIDENTITY)(ob, pp);
}
#endif // WINVER

////////////////////////////////////////////////////////////
//
// RASDIALEXTENSIONS support
//
class PyRASDIALEXTENSIONS : public PyObject
{
public:
	PyRASDIALEXTENSIONS(void);
	~PyRASDIALEXTENSIONS();

	/* Python support */
	static void deallocFunc(PyObject *ob);

	static PyObject *getattro(PyObject *self, PyObject *obname);
	static int setattro(PyObject *self, PyObject *obname, PyObject *v);
	static PyTypeObject type;
	RASDIALEXTENSIONS m_ext;
	PyObject *m_pyeap;
};

#define PyRASDIALEXTENSIONS_Check(ob)	((ob)->ob_type == &PyRASDIALEXTENSIONS::type)

// @object RASDIALEXTENSIONS|An object that describes a Win32 RASDIALEXTENSIONS structure
BOOL PyWinObject_AsRASDIALEXTENSIONS(PyObject *ob, RASDIALEXTENSIONS **ppRASDIALEXTENSIONS, BOOL bNoneOK /*= TRUE*/)
{
	if (bNoneOK && ob==Py_None) {
		*ppRASDIALEXTENSIONS = NULL;
	} else if (!PyRASDIALEXTENSIONS_Check(ob)) {
		PyErr_SetString(PyExc_TypeError, "The object is not a PyRASDIALEXTENSIONS object");
		return FALSE;
	} else {
		*ppRASDIALEXTENSIONS = &((PyRASDIALEXTENSIONS *)ob)->m_ext;
	}
	return TRUE;
}

PyObject *PyWinObject_NewRASDIALEXTENSIONS(PyObject *self, PyObject *args)
{
	if (!PyArg_ParseTuple(args, ""))
		return NULL;
	return new PyRASDIALEXTENSIONS();
}

PyTypeObject PyRASDIALEXTENSIONS::type =
{
	PYWIN_OBJECT_HEAD
	"PyRASDIALEXTENSIONS",
	sizeof(PyRASDIALEXTENSIONS),
	0,
	PyRASDIALEXTENSIONS::deallocFunc,		/* tp_dealloc */
	0,						/* tp_print */
	0,						/* tp_getattr */
	0,						/* tp_setattr */
	0,						/* tp_compare */
	0,						/* tp_repr */
	0,						/* tp_as_number */
	0,						/* tp_as_sequence */
	0,						/* tp_as_mapping */
	0,						/* tp_hahs */
	0,						/* tp_call */
	0,						/* tp_str */
	PyRASDIALEXTENSIONS::getattro,				/* tp_getattro */
	PyRASDIALEXTENSIONS::setattro,				/* tp_setattro */
	0,						/*tp_as_buffer*/
	Py_TPFLAGS_DEFAULT,		// tp_flags;
	"An object that describes a Win32 RASDIALEXTENSIONS structure",		// tp_doc
	0,						// tp_traverse;
	0,						// tp_clear
	0,						// tp_richcompare;
	0,						// tp_weaklistoffset;
	0,						// tp_iter
	0,						// iternextfunc tp_iternext
	0,						// tp_methods
	0,						// tp_members
	0,						// tp_getset;
	0,						// tp_base;
	0,						// tp_dict;
	0,						// tp_descr_get;
	0,						// tp_descr_set;
	0,						// tp_dictoffset;
	0,						// tp_init;
	0,						// tp_alloc;
	0,						// newfunc tp_new;
};


PyRASDIALEXTENSIONS::PyRASDIALEXTENSIONS()
{
	ob_type = &type;
	_Py_NewReference(this);
	m_pyeap = Py_None;
	Py_INCREF(Py_None);
	memset(&m_ext, 0, sizeof(m_ext));
}

PyRASDIALEXTENSIONS::~PyRASDIALEXTENSIONS()
{
	Py_DECREF(m_pyeap);
}

PyObject *PyRASDIALEXTENSIONS::getattro(PyObject *self, PyObject *obname)
{
	char *name=PYWIN_ATTR_CONVERT(obname);
	if (name==NULL)
		return NULL;
	PyRASDIALEXTENSIONS *py = (PyRASDIALEXTENSIONS *)self;
	// @prop integer|dwfOptions|(fOptions may also be used)
	if (strcmp(name, "dwfOptions")==0 || strcmp(name, "fOptions")==0)
		return PyInt_FromLong( py->m_ext.dwfOptions);
	// @prop integer|hwndParent|
	if (strcmp(name, "hwndParent")==0)
		return PyLong_FromVoidPtr( py->m_ext.hwndParent );
	// @prop integer|reserved|
	if (strcmp(name, "reserved")==0)
		return PyWinObject_FromULONG_PTR(py->m_ext.reserved);
#if (WINVER >= 0x500)
	// @prop integer|reserved1|
	if (strcmp(name, "reserved1")==0)
		return PyWinObject_FromULONG_PTR(py->m_ext.reserved1);
	// @prop <o RASEAPINFO>|RasEapInfo|
	if (strcmp(name, "RasEapInfo")==0) {
		Py_INCREF(py->m_pyeap);
		return py->m_pyeap;
	}
#endif
	return PyObject_GenericGetAttr(self, obname);
}

int PyRASDIALEXTENSIONS::setattro(PyObject *self, PyObject *obname, PyObject *val)
{
	if (val == NULL) {
		PyErr_SetString(PyExc_AttributeError, "can't delete OVERLAPPED attributes");
		return -1;
	}
	char *name=PYWIN_ATTR_CONVERT(obname);
	if (name==NULL)
		return -1;
	PyRASDIALEXTENSIONS *py = (PyRASDIALEXTENSIONS *)self;
	if (strcmp(name, "dwfOptions")==0 || strcmp(name, "fOptions")==0) {
		int i=PyInt_AsLong(val);
		if (i==-1 && PyErr_Occurred())
			return -1;
		py->m_ext.dwfOptions = i;
		return 0;
		}
	if (strcmp(name, "hwndParent")==0) {
		HANDLE h;
		if (!PyWinObject_AsHANDLE(val, &h))
			return -1;
		py->m_ext.hwndParent = (HWND)h;
		return 0;
		}
	if (strcmp(name, "reserved")==0) {
		long v = PyInt_AsLong( val );
		if (v==-1 && PyErr_Occurred())
			return -1;
		py->m_ext.reserved = v;
		return 0;
		}
#if (WINVER >= 0x500)
	if (strcmp(name, "reserved1")==0) {
		long v = PyLong_AsLong( val );
		if (v==-1 && PyErr_Occurred())
			return -1;
		py->m_ext.reserved1 = v;
		return 0;
		}
	if (strcmp(name, "RasEapInfo")==0) {
		RASEAPUSERIDENTITY *temp;
		if (!myPyWinObject_AsRASEAPUSERIDENTITY(val, &temp))
			return -1;
		py->m_ext.RasEapInfo.dwSizeofEapInfo = temp->dwSizeofEapInfo;
		py->m_ext.RasEapInfo.pbEapInfo = temp->pbEapInfo;
		Py_DECREF(py->m_pyeap);
		py->m_pyeap = val;
		Py_INCREF(val);
		return 0;
		}
#endif
	return PyObject_GenericSetAttr(self, obname, val);
}

/*static*/ void PyRASDIALEXTENSIONS::deallocFunc(PyObject *ob)
{
	delete (PyRASDIALEXTENSIONS *)ob;
}

////////////////////////////////////////////////////////////
//
// RASDIALPARAMS support
//


// @object RASDIALPARAMS|A tuple that describes a Win32 RASDIALPARAMS structure
// @comm When used as a parameter, RASDIALPARAMS must be a sequence, of up to
// 6 items long.  All items must be strings - None is not allowed.
// <nl>When this is returned from a RAS function, all six fields will exist.
// <nl>RAS will often accept an empty string to mean "default" - ie, passing
// an empty string to phoneNumber uses the stored phone number.
// @tupleitem 0|string|entryName|name of RAS entry.
// @tupleitem 1|string|phoneNumber|phone number to be used.
// @tupleitem 2|string|callBackNumber|phone number to be used if callback is enabled.
// @tupleitem 3|string|userName|username to log on with.
// @tupleitem 4|string|password|password to use
// @tupleitem 5|string|domain|Network domain to log on to.
// @ex An example with win32ras.Dial|handle = win32ras.Dial(None, None, ("Entry Name",), None)
BOOL PyObjectToRasDialParams( PyObject *ob, RASDIALPARAMS *p )
{
	char *fnName = "<RasDialParams conversion>";
	ZeroMemory(p, sizeof(*p));
	p->dwSize = sizeof(RASDIALPARAMS);
	PyObject *t=PySequence_Tuple(ob);
	if (t==NULL)
		return NULL;

	TCHAR *dest, *src;
	Py_ssize_t size = PyTuple_GET_SIZE(ob);
	DWORD dest_size, src_size;
	BOOL ret=TRUE;
	for (Py_ssize_t num=0;num<size;num++) {
		switch (num) {
#define GET_BUF_AND_SIZE(name) dest=p->name;dest_size=sizeof(p->name)/sizeof(p->name[0])
		case 0: GET_BUF_AND_SIZE(szEntryName); break;
		case 1: GET_BUF_AND_SIZE(szPhoneNumber); break;
		case 2: GET_BUF_AND_SIZE(szCallbackNumber); break;
		case 3: GET_BUF_AND_SIZE(szUserName); break;
		case 4: GET_BUF_AND_SIZE(szPassword); break;
		case 5: GET_BUF_AND_SIZE(szDomain); break;
		default:
			SetError("The RasDialParams sequence length must be less than 6", fnName);
			return FALSE;
		}
		PyObject *sub = PyTuple_GET_ITEM(t, num);
		ret=PyWinObject_AsTCHAR(sub, &src, FALSE, &src_size);
		if (!ret)
			break;
		// check it fits in the dest buffer.
		if (src_size >= dest_size) {
			PyErr_Format(PyExc_ValueError, "%s: String size (%d) greater than acceptable size (%d)",
				fnName, src_size, dest_size-1);
			ret = FALSE;
		}
		else
			_tcsncpy(dest, src, src_size);
		PyWinObject_FreeTCHAR(src);
		if (!ret)
			break;
	}
	Py_DECREF(t);
	return ret;
}

/////////////////////////////////////////////////////////////////////
//
// the RAS callback function.  This looks up a Python handler,
// and defers the call to it.
//
// @method |win32ras|RasDialFunc1|A placeholder for a RAS callback.
// @comm Certain RAS function require a callback function to be passed.
// This description describes the signature of the function you pass
// to these functions.
VOID CALLBACK PyRasDialFunc1(
    HRASCONN  hrasconn,	// handle to RAS connection
    UINT  unMsg,	// type of event that has occurred
    RASCONNSTATE  rascs,	// connection state about to be entered
    DWORD  dwError,	// error that may have occurred
    DWORD  dwExtendedError)	// extended error information for some errors
{
	CEnterLeavePython _celp;
	char *fnName = "<RAS Callback handler>";
	PyObject *handler = NULL;
	if (obHandleMap) {
		// NOTE:  As we hold the thread lock, assume noone else can mod this dict.
		PyObject *key = PyWinLong_FromVoidPtr(hrasconn);
		if (key==NULL) return;
		handler = PyDict_GetItem( obHandleMap, key );
		// If handler is NULL, check if None is in the map, and if so,
		// use and replace it.
		if (handler==NULL) {
			handler = PyDict_GetItem( obHandleMap, Py_None );
			if (handler) {
				PyDict_SetItem(obHandleMap, key, handler);
				PyDict_DelItem(obHandleMap, Py_None);
			}
		}
		Py_DECREF(key);
	}
	if (handler==NULL) {
		SetError("Warning - RAS callback has no handler!", fnName);
		PyErr_Print();
		return;
	}
	// @pyparm int|hrascon||The handle to the RAS session.
	// @pyparm int|msg||A message code identifying the reason for the callback.
	// @pyparm int|rascs||Connection state about to be entered.
	// @pyparm int|error||The error state of the connection
	// @pyparm int|extendedError||
	PyObject *args = Py_BuildValue("Niiii", PyWinLong_FromHANDLE(hrasconn), unMsg, rascs, dwError, dwExtendedError);
	if (args==NULL) return;
	PyObject *res = PyEval_CallObject(handler, args);
	Py_DECREF(args);
	if (res==NULL) {
		PyErr_Print();
		SetError("RAS callback failed!", fnName);
		PyErr_Print();
		return;
	}
	Py_DECREF(res);
}

// @pymethod |win32ras|CreatePhonebookEntry|Creates a new phonebook entry.  The function displays a dialog box into which the user can enter information about the entry
static PyObject *
PyRasCreatePhonebookEntry( PyObject *self, PyObject *args )
{
	DWORD rc;
	LPTSTR fileName = NULL;
	PyObject *obhwnd;
	if (!PyArg_ParseTuple(args, "O|s:CreatePhoneBookEntry", 
	          &obhwnd,  // @pyparm int|hWnd||Handle to the parent window of the dialog box.
	          &fileName )) // @pyparm string|fileName|None|Specifies the filename of the phonebook entry.  Currently this is ignored.
		return NULL;
	HWND hwnd;
	if (!PyWinObject_AsHANDLE(obhwnd, (HANDLE *)&hwnd))
		return NULL;
	if (hwnd != 0 && !IsWindow(hwnd))
		return ReturnError("The first parameter must be a valid window handle", "<CreatePhonebookEntry param conversion>");
	if ((rc=RasCreatePhonebookEntry(hwnd, fileName )))
		return ReturnRasError("RasCreatePhonebookEntry",rc);	// @pyseeapi RasCreatePhonebookEntry
	Py_INCREF(Py_None);
	return Py_None;
}

// @pymethod int, int|win32ras|Dial|Establishes a RAS connection to a RAS server.
static PyObject *
PyRasDial( PyObject *self, PyObject *args )
{
	DWORD rc;
	PyObject *obExtensions;
	PyObject *obParams;
	PyObject *obCallback;
	RASDIALPARAMS dialParams;
	RASDIALEXTENSIONS *dialExts;
	LPTSTR fileName;
	HRASCONN hRas = (HRASCONN)0;

	if (!PyArg_ParseTuple(args, "OzOO:Dial", 
	          &obExtensions,  // @pyparm <o PyRASDIALEXTENSIONS>|dialExtensions||An object providing the RASDIALEXTENSIONS information, or None
	          &fileName, // @pyparm string|fileName||Specifies the filename of the phonebook entry, or None.  Ignored on Win95.
	          &obParams,  // @pyparm <o RASDIALPARAMS>|RasDialParams||A tuple describing a RASDIALPARAMS structure.
			  &obCallback))// @pyparm method or hwnd|callback||The method to be called when RAS events occur, or None.  If not None, the function must have the signature of <om win32ras.RasDialFunc1>
		return NULL;
	if (!PyObjectToRasDialParams( obParams, &dialParams ))
		return NULL;

	if (!PyWinObject_AsRASDIALEXTENSIONS( obExtensions, &dialExts, TRUE ))
		return NULL;

	DWORD notType = 0;
	LPVOID pNotification;
	if (obCallback==Py_None) {
		pNotification = NULL;
	} else if (PyCallable_Check(obCallback)) {
		pNotification = PyRasDialFunc1;
		notType = 1;
	} else if (PyInt_Check(obCallback)) {
		if (!PyWinLong_AsVoidPtr(obCallback, &pNotification))
			return NULL;
		notType = 0xFFFFFFFF;
	} else
		return ReturnError("The callback object must be an integer handle, None, or a callable object", "<Dial param parsing>");
	// If we have any sort of callback, we must ensure threads are init'd.
	if (pNotification)
		PyEval_InitThreads();
	// If we have a callback, store it in our map with None as the key.
	// The callback routine will patch this once it knows the true key.
	// Before we do, we must check None is not already there
	if (notType==1) {
		if (obHandleMap==NULL && (obHandleMap = PyDict_New())==NULL)
			return NULL;
		if (PyMapping_HasKey(obHandleMap, Py_None)) {
			PyErr_SetString(PyExc_RuntimeError, "Another RAS callback is in the process of starting");
			return NULL;
		}
		PyDict_SetItem( obHandleMap, Py_None, obCallback );
	}

	// @pyseeapi RasDial
	Py_BEGIN_ALLOW_THREADS
	rc=RasDial( dialExts, fileName, &dialParams, notType, pNotification, &hRas );
	Py_END_ALLOW_THREADS
	if (hRas==0 && notType==1) {
		PyDict_DelItem(obHandleMap, Py_None);
		PyErr_Clear();
	}
	return Py_BuildValue( "Ni", PyWinLong_FromHANDLE(hRas), rc );
	// @rdesc The return value is (handle, retCode).
	// <nl>It is possible for a valid handle to be returned even on failure.
	// <nl>If the returned handle is = 0, then it can be assumed invalid.
	// @comm Note - this handle must be closed using <om win32ras.HangUp>, or
	// else the RAS port will remain open, even after the program has terminated.
	// Your operating system may need rebooting to clean up otherwise!
}

// @pymethod |win32ras|EditPhonebookEntry|Creates a new phonebook entry.  The function displays a dialog box into which the user can enter information about the entry
static PyObject *
PyRasEditPhonebookEntry( PyObject *self, PyObject *args )
{
	DWORD rc;
	LPTSTR fileName;
	LPTSTR entryName;
	PyObject *obhwnd;
	if (!PyArg_ParseTuple(args, "Ozs:EditPhoneBookEntry", 
	          &obhwnd,  // @pyparm int|hWnd||Handle to the parent window of the dialog box.
	          &fileName, // @pyparm string|fileName||Specifies the filename of the phonebook entry, or None.  Currently this is ignored.
	          &entryName )) // @pyparm string|entryName|None|Specifies the name of the phonebook entry to edit
		return NULL;
	HWND hwnd;
	if (!PyWinObject_AsHANDLE(obhwnd, (HANDLE *)&hwnd))
		return NULL;
	if (hwnd != 0 && !IsWindow(hwnd))
		return ReturnError("The first parameter must be a valid window handle", "<EditPhonebookEntry param parsing>");
	Py_BEGIN_ALLOW_THREADS
	rc=RasEditPhonebookEntry(hwnd, fileName, entryName );
	Py_END_ALLOW_THREADS
	if (rc)
		return ReturnRasError("RasEditPhonebookEntry",rc);	// @pyseeapi RasEditPhonebookEntry
	Py_INCREF(Py_None);
	return Py_None;
}

// @pymethod list|win32ras|EnumConnections|Returns a list of tuples, one for each active connection.
static PyObject *
PyRasEnumConnections( PyObject *self, PyObject *args )
{
	DWORD rc;
	DWORD bufSize;
	DWORD noConns = 0;
	RASCONN tc;
	if (!PyArg_ParseTuple(args, ":EnumConnections"))
		return NULL;
	RASCONN *pCon = NULL;
	// make dummy call to determine buffer size.
	tc.dwSize = bufSize = sizeof(RASCONN);
	Py_BEGIN_ALLOW_THREADS
	rc = RasEnumConnections(&tc, &bufSize, &noConns);
	Py_END_ALLOW_THREADS
	if (rc!=0 && rc!=ERROR_BUFFER_TOO_SMALL)
		return ReturnRasError("RasEnumConnections(NULL)", rc);
	if (rc==ERROR_BUFFER_TOO_SMALL) {
		if (bufSize==0)
			return ReturnRasError("RasEnumConnections buffer size is invalid");
		pCon = (RASCONN *)malloc(bufSize);
		if (pCon==NULL) {
			PyErr_SetString(PyExc_MemoryError, "Allocating buffer for RAS connections");
			return NULL;
		}
		// @pyseeapi RasEnumConnections
		pCon[0].dwSize = sizeof(RASCONN);
		Py_BEGIN_ALLOW_THREADS
		rc=RasEnumConnections(pCon, &bufSize, &noConns);
		Py_END_ALLOW_THREADS
		if (rc!=0)
		return ReturnRasError("RasEnumConnections", rc);
	} else {
		pCon = &tc;
	}
	PyObject *ret = PyList_New(noConns);
	if (ret==NULL)
		return NULL;

	for (DWORD i=0;i<noConns;i++){
		PyObject *item=Py_BuildValue("(NNNN)",
			PyWinLong_FromHANDLE(pCon[i].hrasconn),
			PyWinObject_FromTCHAR(pCon[i].szEntryName),
			PyWinObject_FromTCHAR(pCon[i].szDeviceType),
			PyWinObject_FromTCHAR(pCon[i].szDeviceName));
		if (item==NULL){
			Py_DECREF(ret);
			ret=NULL;
			break;
			}
		PyList_SET_ITEM(ret, i, item);
		}
	// @rdesc Each tuple is of format (handle, entryName, deviceType, deviceName)
	if (pCon && pCon != &tc)
		free(pCon);
	return ret;
}

// @pymethod |win32ras|EnumEntries|Returns a list of tuples, one for each phonebook entry.
static PyObject *
PyRasEnumEntries( PyObject *self, PyObject *args )
{
	DWORD rc;
	DWORD bufSize=3*sizeof(RASENTRYNAME);
	RASENTRYNAME *buf = NULL;
	DWORD noConns = 0, i;
	TCHAR *reserved = NULL;
	TCHAR *bookName = NULL;
	PyObject *obreserved=Py_None, *obbookName=Py_None, *ret=NULL;

	if (!PyArg_ParseTuple(args, "|OO:EnumEntries",
		       &obreserved, // @pyparm string|reserved|None|Reserved - must be None
			   &obbookName)) // @pyparm string|fileName|None|The name of the phonebook file, or None.
		return NULL;
	if (!PyWinObject_AsTCHAR(obreserved, &reserved, TRUE) ||
		!PyWinObject_AsTCHAR(obbookName, &bookName, TRUE))
		goto cleanup;

	while (true){
		if (buf)
			free(buf);
		buf=(RASENTRYNAME *)malloc(bufSize);
		if (buf==NULL){
			PyErr_NoMemory();
			goto cleanup;
			}
		// ??? Not sure if this is needed, only sets the size of first struct in buf ???
		buf->dwSize = sizeof(RASENTRYNAME);
		Py_BEGIN_ALLOW_THREADS
		rc=RasEnumEntries(reserved, bookName, buf, &bufSize, &noConns);
		Py_END_ALLOW_THREADS
		if (rc==0)
			break;
		if (rc==ERROR_BUFFER_TOO_SMALL)
			continue;
		ReturnRasError("RasEnumEntries", rc);
		goto cleanup;
		}

	ret = PyTuple_New(noConns);
	if (!ret)
		goto cleanup;
	for (i=0;i<noConns;i++){
		PyObject *item=PyWinObject_FromTCHAR(buf[i].szEntryName);
		// ??? This struct now has some extra data ???
		if (item==NULL){
			Py_DECREF(ret);
			ret=NULL;
			break;
			}
		PyTuple_SET_ITEM(ret, i, item);
		}

cleanup:
	if (buf)
		free(buf);
	PyWinObject_FreeTCHAR(reserved);
	PyWinObject_FreeTCHAR(bookName);
	return ret;
}

// @pymethod (int, int, string, string)|win32ras|GetConnectStatus|Returns a tuple with connection information.
static PyObject *
PyRasGetConnectStatus( PyObject *self, PyObject *args )
{
	DWORD rc;
	PyObject *obras;
	if (!PyArg_ParseTuple(args, "O:GetConnectStatus", 
	          &obras ))  // @pyparm int|hrasconn||Handle to the RAS session.
		return NULL;
	HRASCONN hras;
	if (!PyWinObject_AsHANDLE(obras, (HANDLE *)&hras))
		return NULL;
	RASCONNSTATUS cs;
	// @pyseeapi RasGetConnectStatus
	cs.dwSize = sizeof(RASCONNSTATUS);
	if ((rc=RasGetConnectStatus(hras, &cs )))
		return ReturnRasError("RasGetConnectStatus",rc);	// @pyseeapi RasGetConnectStatus
	return Py_BuildValue("(iiss)", cs.rasconnstate, cs.dwError, cs.szDeviceType, cs.szDeviceName);
}

// @pymethod (s,s,s,s,s,s),i|win32ras|GetEntryDialParams|Returns a tuple with the most recently set dial parameters for the specified entry.
static PyObject *
PyRasGetEntryDialParams( PyObject *self, PyObject *args )
{
	TCHAR *fileName=NULL;
	TCHAR *entryName=NULL;
	PyObject *obfileName, *obentryName, *ret=NULL;
	DWORD rc;
	if (!PyArg_ParseTuple(args, "OO:GetEntryDialParams", 
	          &obfileName, // @pyparm string|fileName||The filename of the phonebook, or None.
			  &obentryName))  // @pyparm string|entryName||The name of the entry to retrieve the params for.
		return NULL;

	if (PyWinObject_AsTCHAR(obfileName, &fileName, TRUE)
		&& PyWinObject_AsTCHAR(obentryName, &entryName, FALSE)){
		RASDIALPARAMS dp;
		BOOL bPass;
		dp.dwSize = sizeof(RASDIALPARAMS);
		_tcsncpy(dp.szEntryName, entryName, RAS_MaxEntryName + 1);
		dp.szEntryName[RAS_MaxEntryName] = '\0';
		// @pyseeapi RasGetEntryDialParams
		if ((rc=RasGetEntryDialParams(fileName, &dp, &bPass )))
			ReturnRasError("RasGetEntryDialParams",rc);	// @pyseeapi RasGetConnectStatus
		else
			ret = Py_BuildValue("(NNNNNN),N",
				PyWinObject_FromTCHAR(dp.szEntryName),
				PyWinObject_FromTCHAR(dp.szPhoneNumber),
				PyWinObject_FromTCHAR(dp.szCallbackNumber),
				PyWinObject_FromTCHAR(dp.szUserName), 
				PyWinObject_FromTCHAR(dp.szPassword),
				PyWinObject_FromTCHAR(dp.szDomain),
				PyBool_FromLong(bPass));
		}
	PyWinObject_FreeTCHAR(fileName);
	PyWinObject_FreeTCHAR(entryName);
	return ret;
	// @rdesc The return value is a tuple describing the params retrieved, plus a BOOL integer
	// indicating if the password was also retrieved.
}

// @pymethod string|win32ras|GetErrorString|Returns an error string for a RAS error code.
static PyObject *
PyRasGetErrorString( PyObject *self, PyObject *args )
{
	DWORD error;
	DWORD rc;
	if (!PyArg_ParseTuple(args, "i:GetErrorString", 
	          &error)) // @pyparm int|error||The error value being queried.
		return NULL;

	TCHAR buf[512];
	// @pyseeapi RasGetErrorString
	if (rc=RasGetErrorString(error, buf, sizeof(buf)/sizeof(buf[0])))
		return ReturnRasError("RasGetErrorString");
	return PyWinObject_FromTCHAR(buf);
}

// @pymethod |win32ras|HangUp|Terminates a remote access session.
static PyObject *
PyRasHangUp( PyObject *self, PyObject *args )
{
	DWORD rc;
	HRASCONN hras;
	if (!PyArg_ParseTuple(args, "O&:HangUp", 
	          PyWinObject_AsHANDLE, &hras)) // @pyparm int|hras||The handle to the RAS connection to be terminated.
		return NULL;

	// @pyseeapi RasHangUp
	if (rc=RasHangUp(hras))
		return ReturnRasError("RasHangup");
	Py_INCREF(Py_None);
	return Py_None;
}


// @pymethod |win32ras|IsHandleValid|Indicates if the given RAS handle is valid.
static PyObject *
PyRasIsHandleValid( PyObject *self, PyObject *args )
{
	HRASCONN hras;
	if (!PyArg_ParseTuple(args, "O&:IsHandleValid", 
	          PyWinObject_AsHANDLE, &hras)) // @pyparm int|hras||The handle to the RAS connection being checked.
		return NULL;
	BOOL bRet = (hras>=0);
	return PyBool_FromLong(bRet);
}


// @pymethod |win32ras|SetEntryDialParams|Sets the dial parameters for the specified entry.
static PyObject *
PyRasSetEntryDialParams( PyObject *self, PyObject *args )
{
	TCHAR *fileName;
	PyObject *obfileName, *obParams;
	RASDIALPARAMS dialParams;
	DWORD rc;
	BOOL bRemPass;
	if (!PyArg_ParseTuple(args, "OOi:SetEntryDialParams", 
	          &obfileName, // @pyparm string|fileName||The filename of the phonebook, or None.
			  &obParams,// @pyparm (tuple)|RasDialParams||A tuple describing a RASDIALPARAMS structure.
			  &bRemPass)) // @pyparm int|bSavePassword||Indicates whether to remove password from entry's parameters.
		return NULL;

	if (!PyObjectToRasDialParams( obParams, &dialParams ))
		return NULL;
	if (!PyWinObject_AsTCHAR(obfileName, &fileName,TRUE))
		return NULL;
	// @pyseeapi RasSetEntryDialParams
	rc=RasSetEntryDialParams(fileName, &dialParams, bRemPass);
	PyWinObject_FreeTCHAR(fileName);
	if (rc)
		return ReturnRasError("SetEntryDialParams",rc);	// @pyseeapi RasGetConnectStatus
	Py_INCREF(Py_None);
	return Py_None;
}




/* List of functions exported by this module */
// @module win32ras|A module encapsulating the Windows Remote Access Service (RAS) API.
static struct PyMethodDef win32ras_functions[] = {
	{"CreatePhonebookEntry",        PyRasCreatePhonebookEntry,  METH_VARARGS}, // @pymeth CreatePhonebookEntry|Creates a new phonebook entry.  The function displays a dialog box into which the user can enter information about the entry.
	{"Dial",                        PyRasDial,  METH_VARARGS}, // @pymeth Dial|Establishes a RAS connection to a RAS server.
	{"EditPhonebookEntry",          PyRasEditPhonebookEntry, METH_VARARGS}, // @pymeth EditPhonebookEntry|Creates a new phonebook entry.  The function displays a dialog box into which the user can enter information about the entry
	{"EnumConnections",             PyRasEnumConnections, METH_VARARGS}, // @pymeth EnumConnections|Returns a list of tuples, one for each active connection.
	{"EnumEntries",                 PyRasEnumEntries, METH_VARARGS}, // @pymeth EnumEntries|Returns a list of tuples, one for each phonebook entry.
	{"GetConnectStatus",            PyRasGetConnectStatus, METH_VARARGS}, // @pymeth GetConnectStatus|Returns a tuple with connection information.
	{"GetEntryDialParams",          PyRasGetEntryDialParams, METH_VARARGS}, // @pymeth GetEntryDialParams|Returns a tuple with the most recently set dial parameters for the specified entry.
	{"GetErrorString",              PyRasGetErrorString, METH_VARARGS}, // @pymeth GetErrorString|Returns an error string for a RAS error code.
	{"HangUp",                      PyRasHangUp, METH_VARARGS}, // @pymeth HangUp|Terminates a remote access session.
	{"IsHandleValid",				PyRasIsHandleValid, METH_VARARGS}, // @pymeth IsHandleValid|Indicates if the given RAS handle is valid.
	{"SetEntryDialParams",          PyRasSetEntryDialParams, METH_VARARGS}, // @pymeth SetEntryDialParams|Sets the dial parameters for the specified entry.
	{"RASDIALEXTENSIONS",           PyWinObject_NewRASDIALEXTENSIONS, METH_VARARGS}, // @pymeth RASDIALEXTENSIONS|Creates a new <o RASDIALEXTENSIONS> object
	{NULL,			NULL}
};

#define ADD_CONSTANT(tok) if (rc = PyModule_AddIntConstant(module, #tok, tok)) return rc
#define ADD_ENUM(parta, partb) if (rc = PyModule_AddIntConstant(module, #parta "_" #partb, parta::partb)) return rc
#define ADD_ENUM3(parta, partb, partc) if (rc = PyModule_AddIntConstant(module, #parta "_" #partb "_" #partc, parta::partb::partc)) return rc

static int AddConstants(PyObject *module)
{
    int rc;
    ADD_CONSTANT(RASCS_OpenPort); // @const win32ras|RASCS_OpenPort|Constant for RAS state.
    ADD_CONSTANT(RASCS_PortOpened); // @const win32ras|RASCS_PortOpened|Constant for RAS state.
    ADD_CONSTANT(RASCS_ConnectDevice); // @const win32ras|RASCS_ConnectDevice|Constant for RAS state.
    ADD_CONSTANT(RASCS_DeviceConnected); // @const win32ras|RASCS_DeviceConnected|Constant for RAS state.
    ADD_CONSTANT(RASCS_AllDevicesConnected); // @const win32ras|RASCS_AllDevicesConnected|Constant for RAS state.
    ADD_CONSTANT(RASCS_Authenticate); // @const win32ras|RASCS_Authenticate|Constant for RAS state.
    ADD_CONSTANT(RASCS_AuthNotify); // @const win32ras|RASCS_AuthNotify|Constant for RAS state.
    ADD_CONSTANT(RASCS_AuthRetry); // @const win32ras|RASCS_AuthRetry|Constant for RAS state.
    ADD_CONSTANT(RASCS_AuthCallback); // @const win32ras|RASCS_AuthCallback|Constant for RAS state.
    ADD_CONSTANT(RASCS_AuthChangePassword); // @const win32ras|RASCS_AuthChangePassword|Constant for RAS state.
    ADD_CONSTANT(RASCS_AuthProject); // @const win32ras|RASCS_AuthProject|Constant for RAS state.
    ADD_CONSTANT(RASCS_AuthLinkSpeed); // @const win32ras|RASCS_AuthLinkSpeed|Constant for RAS state.
    ADD_CONSTANT(RASCS_AuthAck); // @const win32ras|RASCS_AuthAck|Constant for RAS state.
    ADD_CONSTANT(RASCS_ReAuthenticate); // @const win32ras|RASCS_ReAuthenticate|Constant for RAS state.
    ADD_CONSTANT(RASCS_Authenticated); // @const win32ras|RASCS_Authenticated|Constant for RAS state.
    ADD_CONSTANT(RASCS_PrepareForCallback); // @const win32ras|RASCS_PrepareForCallback|Constant for RAS state.
    ADD_CONSTANT(RASCS_WaitForModemReset); // @const win32ras|RASCS_WaitForModemReset|Constant for RAS state.
    ADD_CONSTANT(RASCS_WaitForCallback); // @const win32ras|RASCS_WaitForCallback|Constant for RAS state.
    ADD_CONSTANT(RASCS_Projected); // @const win32ras|RASCS_Projected|Constant for RAS state.
    ADD_CONSTANT(RASCS_StartAuthentication); // @const win32ras|RASCS_StartAuthentication|Constant for RAS state.
    ADD_CONSTANT(RASCS_CallbackComplete); // @const win32ras|RASCS_CallbackComplete|Constant for RAS state.
    ADD_CONSTANT(RASCS_LogonNetwork); // @const win32ras|RASCS_LogonNetwork|Constant for RAS state.
    ADD_CONSTANT(RASCS_Interactive); // @const win32ras|RASCS_Interactive|Constant for RAS state.
    ADD_CONSTANT(RASCS_RetryAuthentication); // @const win32ras|RASCS_RetryAuthentication|Constant for RAS state.
    ADD_CONSTANT(RASCS_CallbackSetByCaller); // @const win32ras|RASCS_CallbackSetByCaller|Constant for RAS state.
    ADD_CONSTANT(RASCS_PasswordExpired); // @const win32ras|RASCS_PasswordExpired|Constant for RAS state.
    ADD_CONSTANT(RASCS_Connected); // @const win32ras|RASCS_Connected|Constant for RAS state.
    ADD_CONSTANT(RASCS_Disconnected); // @const win32ras|RASCS_Disconnected|Constant for RAS state.
    return 0;
}

PYWIN_MODULE_INIT_FUNC(win32ras)
{
	PYWIN_MODULE_INIT_PREPARE(win32ras, win32ras_functions,
				  "A module encapsulating the Windows Remote Access Service (RAS) API.");

	module_error = PyWinExc_ApiError;
	Py_INCREF(module_error);
	PyDict_SetItemString(dict, "error", module_error);
	if (PyType_Ready(&PyRASDIALEXTENSIONS::type) == -1)
		PYWIN_MODULE_INIT_RETURN_ERROR;
	if (AddConstants(module) != 0)
		PYWIN_MODULE_INIT_RETURN_ERROR;

	PYWIN_MODULE_INIT_RETURN_SUCCESS;
}
